//
//  functions.cpp
//  
//
//  Created by Nikita Luzyanin on 30.11.13.
//
//

#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
using namespace std;

#define META_BEFORE_CONTENTS 1

int copySrcToDst(char *pathToFS, char *pathToSrc, char *pathToDst);
int remove(char *pathToFS, char *pathInFS);
int fillWithZero(char *pathToFS, char *block, int from);

int findFreeBlock(char *pathToFS, char *freeBlock, bool blocking)
{
    FILE *f;
    char pathToBlock[1000];
    char number[15];
    char dat;
    
    for (int i = 0; i > -1 ;i++) {
        
        sprintf(number, "%i", i);
        strcpy(pathToBlock, pathToFS);
        strcat(pathToBlock, "/");
        strcat(pathToBlock, number);
        f = fopen(pathToBlock, "r+b");
        if (f == NULL) {
            fclose(f);
            return 1;
        } else {
            fread(&dat, sizeof(char), 1, f);
            if (dat == '0') {
                if (blocking == true) {
                    fstream OF;
                    OF.open(pathToBlock);
                    OF << '/';
                    OF.close();
                }
                strcpy(freeBlock, "/");
                strcat(freeBlock, number);
                fclose(f);
                return 0;
            }
        }
        fclose(f);
    }
    return 1;
}

int createNewFile(char *pathToFS, char *name, char *pathToParent)
{
    time_t seconds = time(NULL);
    tm* timeinfo = localtime(&seconds);
    
    fstream F;
    char pathToMetaBlock[1000], metaBlock[10], pathToContentBlock[1000], contentBlock[10];
    if (findFreeBlock(pathToFS, metaBlock, true) != 0) {
        cout << "No more free blocks\n";
    }
    strcpy(pathToMetaBlock, pathToFS);
    strcat(pathToMetaBlock, metaBlock);
    if (findFreeBlock(pathToFS, contentBlock, true) != 0) {
        cout << "No more free blocks\n";
    }
    strcpy(pathToContentBlock, pathToFS);
    strcat(pathToContentBlock, contentBlock);
    
    F.open(pathToMetaBlock);
    F << "/";
    F << name;
    F << "\n";
    F << contentBlock;
    F << "\n";
    F << pathToParent;
    F << "\n";
    F << "0\n"; // size
    F << asctime(timeinfo); // time of change
    F.close();
    
    F.open(pathToContentBlock);
    // F << "next 0\n";
    F << "n\n";
    F.close();
    
    return 0;
}

int changeSizeAndDate(char *pathToFS, char *metaBlock, int size, bool newSize)
{
    char pathToMetaBlock[1000], helper[20], parentMetaBlock[10];
    fstream MB;
    strcpy(parentMetaBlock, metaBlock);
    
    while (strcmp(parentMetaBlock,"n") != 0) {
        strcpy(pathToMetaBlock, pathToFS);
        strcat(pathToMetaBlock, parentMetaBlock);
        MB.open(pathToMetaBlock);
        MB >> helper;
        MB >> helper;
        MB >> parentMetaBlock;
        int currentPositionMB = MB.tellg();
        char sizeC[10];
        int sizeI;
        if (newSize == false) {
            MB >> sizeC;
            MB.seekp(currentPositionMB);
            sizeI = atoi(sizeC);
            sizeI = sizeI + size;
        } else {
            sizeI = size;
        }
        sprintf(sizeC, "%i", sizeI);
        MB << "\n";
        MB << sizeC;
        MB << "\n";
        time_t seconds = time(NULL);
        tm* timeinfo = localtime(&seconds);
        MB << asctime(timeinfo);
        currentPositionMB = MB.tellg();
        MB.close();
        fillWithZero(pathToFS, metaBlock, currentPositionMB + 6);
    } ;
    return 0;
}

int fileExists(char partOfPathInFS[11], char *pathToFS, char *metaBlock, bool createIfNeeded, bool isFolder, bool isFile)
{
    fstream MB;
    char contentName[11], contentBlock[10], helper[12], ch, pathToContentBlock[1000], pathToMetaBlock[1000], pathToContent[10], parentMetaBlock[10];
    int currentPositionMB, currentPositionCB;
    strcpy(pathToMetaBlock, pathToFS);
    strcat(pathToMetaBlock, metaBlock);
    strcpy(parentMetaBlock, metaBlock);
    MB.open(pathToMetaBlock);
    for (int j = 0; j < META_BEFORE_CONTENTS; j++) {
        MB >> helper;
    }
    MB >> contentBlock;
    
    fstream CB;
    while (strcmp(contentBlock, "n")) {
        
        strcpy(pathToContentBlock, pathToFS);
        strcat(pathToContentBlock, contentBlock);
        CB.open(pathToContentBlock);
        
        CB >> contentBlock;
        
        ch = CB.get();
        ch = CB.peek();
        while (ch == '/') {
            ch = CB.get();
            CB >> contentName;
            CB >> pathToContent;
            if ((isFolder == false)&&(isFile == false)) {
                if (contentName[strlen(contentName)-1] == '/') {
                    contentName[strlen(contentName)-1] = '\0';
                }
            }
            if (strcmp(partOfPathInFS, contentName) == 0) {
                strcpy(metaBlock, pathToContent);
                CB.close();
                MB.close();
                return 0;
            }
            ch = CB.get();
            ch = CB.peek();
            // skips eol and '/' to next content
        }
        currentPositionCB = CB.tellg();
        CB.close();
    }
    /*currentPositionMB = MB.tellg();
    char sizeC[10];
    MB >> sizeC;*/
    MB.close();
    
    if (createIfNeeded == true) {
        
        changeSizeAndDate(pathToFS, parentMetaBlock, 0, false);
        
        strcpy(metaBlock, "/");
        if (findFreeBlock(pathToFS, metaBlock, false) != 0) {
            cout << "No more free blocks\n";
        }
        char newContent[20];
        strcpy(newContent, "/");
        strcat(newContent, partOfPathInFS);
        strcat(newContent, " ");
        strcat(newContent, metaBlock);
        strcat(newContent, "\n");
        
        CB.open(pathToContentBlock);
        CB.seekp(currentPositionCB);
        CB << newContent;
        CB.close();
        
        if (createNewFile(pathToFS, partOfPathInFS, parentMetaBlock) != 0) {
            cout << "block was not formatted\n";
        }
    }
    return 1;
}

int findMetaBlock(char *pathToFS, char *metaBlock, char *pathInFS, bool createIfNeeded, bool isFolder, bool isFile)
{
    strcpy(metaBlock, "/0");
    if (strcmp(pathInFS, "/0") != 0) {
        char *helper;
        char partOfPathInFS[100][11];
        int depthOfPathInFS = 0;
        helper = strtok(pathInFS, "/");
        while (helper != NULL) {
            if (strlen(helper) > 10) {
                cout << "Name is too long\n";
                return 1;
            }
            strcpy(partOfPathInFS[depthOfPathInFS], helper);
            helper = strtok(NULL, "/");
            if (helper != NULL) {
                strcat(partOfPathInFS[depthOfPathInFS], "/");
            } else {
                if (isFolder == true) {
                    strcat(partOfPathInFS[depthOfPathInFS], "/");
                }
            }
            depthOfPathInFS++;
        }
        int i;
        for (i = 0; i < depthOfPathInFS - 1; i++) {
            if (fileExists(partOfPathInFS[i], pathToFS, metaBlock, createIfNeeded, true, false) != 0) {
                if (createIfNeeded == false) {
                    return 1;
                }
            }
        }
        if (fileExists(partOfPathInFS[i], pathToFS, metaBlock, createIfNeeded, isFolder, isFile) != 0) {
            if (createIfNeeded == false) {
                return 1;
            }
        }
    }
    return 0;
}

int findContentBlock(char *pathToFS, char *metaBlock, char *contentBlock)
{
    fstream MB;
    char pathToMetaBlock[1000], helper[12];
    int currentPositionMB, currentPositionCB;
    strcpy(pathToMetaBlock, pathToFS);
    strcat(pathToMetaBlock, metaBlock);
    MB.open(pathToMetaBlock);
    for (int j = 0; j < META_BEFORE_CONTENTS; j++) {
        MB >> helper;
    }
    MB >> contentBlock;
    MB.close();
    return 0;
}

int fillWithZero(char *pathToFS, char *block, int from)
{
    int sizeOfBlock;
    fstream config;
    config.open("config");
    config >> sizeOfBlock;
    config.close();
    
    char pathToBlock[1000];
    strcpy(pathToBlock, pathToFS);
    strcat(pathToBlock, block);
    fstream B;
    B.open(pathToBlock);
    B.seekp(from);
    while (B.tellg() < sizeOfBlock) {
        B << "0";
    }
    B.close();
    
    return 0;
}

int clearContentBlocks(char *pathToFS, char *metaBlock, char *contentBlock, bool format)
{
    char pathToContentBlock[1000], nextContentBlock[10], currContentBlock[10], pathToMetaBlock[1000], helper[10];
    int size;
    fstream B;
    strcpy(nextContentBlock, contentBlock);
    
    strcpy(pathToMetaBlock, pathToFS);
    strcat(pathToMetaBlock, metaBlock);
    B.open(pathToMetaBlock);
    B >> helper;
    B >> helper;
    B >> helper;
    B >> size;
    B.close();
    
    
    while (strcmp(nextContentBlock, "n")) {
        
        strcpy(pathToContentBlock, pathToFS);
        strcat(pathToContentBlock, nextContentBlock);
        strcpy(currContentBlock, nextContentBlock);
        B.open(pathToContentBlock);
        B >> nextContentBlock;
        B.close();
        fillWithZero(pathToFS, currContentBlock, 0);
    }
    
    changeSizeAndDate(pathToFS, metaBlock, 0 - size, false);
    if (format == true) {
        strcpy(pathToContentBlock, pathToFS);
        strcat(pathToContentBlock, contentBlock);
        B.open(pathToContentBlock);
        B << "n\n";
        B.close();
    }
    
    return 0;
}

int copyFile(char *pathToFS, char *pathToSrc, char *pathToDst)
{
    char srcMetaBlock[10], srcContentBlock[10];
    if (findMetaBlock(pathToFS, srcMetaBlock, pathToSrc, false, false, true) != 0) {
        cout << "No File in FS\n";
        return 1;
    }
    if (findContentBlock(pathToFS, srcMetaBlock, srcContentBlock) != 0) {
        cout << "No Content File\n";
        return 1;
    }
    char pathToSrcContentBlock[1000], pathToSrcMetaBlock[1000], helper[20];
    strcpy(pathToSrcContentBlock, pathToFS);
    strcat(pathToSrcContentBlock, srcContentBlock);
    strcpy(pathToSrcMetaBlock, pathToFS);
    strcat(pathToSrcMetaBlock, srcMetaBlock);
    
    char dstMetaBlock[10], dstContentBlock[10];
    if (findMetaBlock(pathToFS, dstMetaBlock, pathToDst, true, false, true) != 0) {
        cout << "Mistake\n";
        return 1;
    }
    if (findContentBlock(pathToFS, dstMetaBlock, dstContentBlock) != 0) {
        cout << "Mistake\n";
        return 1;
    }
    char pathToDstContentBlock[1000], pathToDstMetaBlock[1000];
    strcpy(pathToDstContentBlock, pathToFS);
    strcat(pathToDstContentBlock, dstContentBlock);
    strcpy(pathToDstMetaBlock, pathToFS);
    strcat(pathToDstMetaBlock, dstMetaBlock);
    
    char ch, nextSrcContentBlock[10], nextDstContentBlock[10];
    if (clearContentBlocks(pathToFS, dstMetaBlock, dstContentBlock, true) != 0) {
        cout << "Mistake!\n";
        return 1;
    }
    
    int size;
    fstream MB;
    MB.open(pathToSrcMetaBlock);
    MB >> helper;
    MB >> helper;
    MB >> helper;
    MB >> size;
    MB.close();
    
    changeSizeAndDate(pathToFS, dstMetaBlock, size, false);
    fstream srcB, dstB;
    srcB.open(pathToSrcContentBlock);
    dstB.open(pathToDstContentBlock);
    srcB >> nextSrcContentBlock;
    
    int sizeOfBlock;
    fstream config;
    config.open("config");
    config >> sizeOfBlock;
    config.close();
    
    int additionalNumberOfBlocks = size/sizeOfBlock;
    int blocksAdded = 0;
    int bytesWritten = 0;
    int bytesExtracted = 0;
    if (additionalNumberOfBlocks > 0) {
        if (findFreeBlock(pathToFS, nextDstContentBlock, true) != 0) {
            cout << "No more free blocks\n";
        }
        dstB << nextDstContentBlock;
        dstB << "\n";
        bytesWritten ++;
    } else {
        dstB >> nextDstContentBlock;
        int currentPositionDstB = dstB.tellg();
        dstB.seekp(currentPositionDstB + 1);
    }
    ch = srcB.get();
    ch = srcB.get();
    while (bytesExtracted < size) {
        if (!srcB.eof()) {
            if ((bytesWritten + strlen(nextDstContentBlock)) < ((sizeOfBlock) * (blocksAdded + 1))) {
                dstB << ch;
                bytesWritten ++;
                ch = srcB.get();
                bytesExtracted ++;
            } else {
                dstB.close();
                strcpy(pathToDstContentBlock, pathToFS);
                strcat(pathToDstContentBlock, nextDstContentBlock);
                dstB.open(pathToDstContentBlock);
                if (additionalNumberOfBlocks > (blocksAdded + 1)) {
                    if (findFreeBlock(pathToFS, nextDstContentBlock, true) != 0) {
                        cout << "No more free blocks\n";
                    }
                    dstB << nextDstContentBlock;
                    dstB << "\n";
                    bytesWritten = bytesWritten + 3;
                } else {
                    dstB << "n\n";
                    bytesWritten = bytesWritten + 2;
                    strcpy(nextDstContentBlock, "n");
                }
                blocksAdded ++;
            }
        } else {
            srcB.close();
            strcpy(pathToSrcContentBlock, pathToFS);
            strcat(pathToSrcContentBlock, nextSrcContentBlock);
            srcB.open(pathToSrcContentBlock);
            srcB >> nextSrcContentBlock;
            ch = srcB.get();
            ch = srcB.get();
        }
    }
    dstB << " ";
    while ((bytesWritten + strlen(nextDstContentBlock)) < sizeOfBlock - 2) {
        dstB << "0";
        bytesWritten ++;
    }
    srcB.close();
    dstB.close();
    
    return 0;
}

int copyFolder(char *pathToFS, char *pathToSrc, char *pathToDst)
{
    char initialPathToSrc[1000], initialPathToDst[1000];
    strcpy(initialPathToSrc, pathToSrc);
    strcpy(initialPathToDst, pathToDst);
    
    char srcMetaBlock[10], srcContentBlock[10];
    if (findMetaBlock(pathToFS, srcMetaBlock, pathToSrc, false, true, false) != 0) {
        cout << "No File in FS\n";
        return 1;
    }
    if (findContentBlock(pathToFS, srcMetaBlock, srcContentBlock) != 0) {
        cout << "No Content File\n";
        return 1;
    }
    char pathToSrcContentBlock[1000], pathToSrcMetaBlock[1000], helper[20];
    strcpy(pathToSrcContentBlock, pathToFS);
    strcat(pathToSrcContentBlock, srcContentBlock);
    strcpy(pathToSrcMetaBlock, pathToFS);
    strcat(pathToSrcMetaBlock, srcMetaBlock);
    
    char dstMetaBlock[10], dstContentBlock[10];
    if (findMetaBlock(pathToFS, dstMetaBlock, pathToDst, true, true, false) != 0) {
        cout << "Mistake\n";
        return 1;
    }
    if (findContentBlock(pathToFS, dstMetaBlock, dstContentBlock) != 0) {
        cout << "Mistake\n";
        return 1;
    }
    char pathToDstContentBlock[1000], pathToDstMetaBlock[1000];
    strcpy(pathToDstContentBlock, pathToFS);
    strcat(pathToDstContentBlock, dstContentBlock);
    strcpy(pathToDstMetaBlock, pathToFS);
    strcat(pathToDstMetaBlock, dstMetaBlock);
    
    fstream srcB, dstB;
    char contentName[12], pathToContent[1000], ch, nextSrcContentBlock[1000], nextDstContentBlock[1000];
    srcB.open(pathToSrcContentBlock);
    dstB.open(pathToDstContentBlock);
    srcB >> nextSrcContentBlock;
    dstB << "n\n";
    ch = srcB.get();
    ch = srcB.peek();
    while (ch == '/') {
        ch = srcB.get();
        srcB >> contentName;
        srcB >> pathToContent;
        if (findFreeBlock(pathToFS, nextDstContentBlock, false) != 0) {
            cout << "No more free blocks\n";
        }
        
        char newPathToSrc[1000], newPathToDst[1000];
        strcpy(newPathToSrc, initialPathToSrc);
        strcat(newPathToSrc, contentName);
        strcpy(newPathToDst, initialPathToDst);
        strcat(newPathToDst, contentName);
        copySrcToDst(pathToFS, newPathToSrc, newPathToDst);
        
        ch = srcB.get();
        ch = srcB.peek();
        // skips eol and '/' to next content
    }
    
    srcB.close();
    dstB.close();
    
    return 0;
}

int copySrcToDst(char *pathToFS, char *pathToSrc, char *pathToDst)
{
    bool isFolder, isFile;
    
    if (pathToSrc[strlen(pathToSrc)-1] == '/') {
        isFolder = true;
        isFile = false;
    } else {
        isFolder = false;
        isFile = true;
    }
    if (isFile == true) {
        if (copyFile(pathToFS, pathToSrc, pathToDst) != 0) {
            cout << "Mistake\n";
            return 1;
        }
    } else {
        if (copyFolder(pathToFS, pathToSrc, pathToDst) != 0) {
            cout << "Mistake\n";
            return 1;
        }
    }
    return 0;
}

int removeFile(char *pathToFS, char *pathInFS)
{
    char metaBlock[10], contentBlock[10];
    if (findMetaBlock(pathToFS, metaBlock, pathInFS, false, false, false) != 0) {
        cout << "No File in FS\n";
        return 1;
    }
    if (findContentBlock(pathToFS, metaBlock, contentBlock) != 0) {
        cout << "No Content File\n";
        return 1;
    }
    char pathToContentBlock[1000], pathToMetaBlock[1000], helper[20];
    strcpy(pathToContentBlock, pathToFS);
    strcat(pathToContentBlock, contentBlock);
    strcpy(pathToMetaBlock, pathToFS);
    strcat(pathToMetaBlock, metaBlock);
    
    if (clearContentBlocks(pathToFS, metaBlock, contentBlock, false) != 0) {
        cout << "Mistake!\n";
        return 1;
    }
    
    fstream F;
    int size;
    char parentContentBlock[10], name[12], parentMetaBlock[10];
    F.open(pathToMetaBlock);
    F >> name;
    F >> helper;
    F >> parentMetaBlock;
    F >> size;
    F.close();
    
    if (findContentBlock(pathToFS, parentMetaBlock, parentContentBlock) != 0) {
        cout << "No Content File\n";
        return 1;
    }
    
    char contentName[12], pathToContent[10], ch, nextBlock[10];
    char allContents[1000][12];
    int i = 0;
    strcpy(pathToContentBlock, pathToFS);
    strcat(pathToContentBlock, parentContentBlock);
    F.open(pathToContentBlock);
    F >> nextBlock;
    ch = F.get();
    ch = F.peek();
    while (ch == '/') {
        // ch = F.get();
        F >> contentName;
        F >> pathToContent;
        if (strcmp(contentName, name) != 0) {
            strcpy(allContents[i], contentName);
            i++;
            strcpy(allContents[i], pathToContent);
            i++;
        }
        ch = F.get();
        ch = F.peek();
        // skips eol and '/' to next content
    }
    
    F.close();
    
    changeSizeAndDate(pathToFS, metaBlock, 0 - size, false);
    fillWithZero(pathToFS, parentContentBlock, 0);
    
    int j = 0;
    F.open(pathToContentBlock);
    F << nextBlock;
    F << "\n";
    while (j < i) {
        F << allContents[j];
        F << " ";
        j++;
        F << allContents[j];
        F << "\n";
        j++;
    }
    F.close();
    
    fillWithZero(pathToFS, metaBlock, 0);
    
    return 0;
}

int removeFolder(char *pathToFS, char *pathInFS)
{
    char initialPathInFS[1000];
    strcpy(initialPathInFS, pathInFS);
    
    
    char metaBlock[10], contentBlock[10];
    if (findMetaBlock(pathToFS, metaBlock, pathInFS, false, true, false) != 0) {
        cout << "No File in FS\n";
        return 1;
    }
    if (findContentBlock(pathToFS, metaBlock, contentBlock) != 0) {
        cout << "No Content File\n";
        return 1;
    }
    char helper[20], pathToContentBlock[1000];
    char contentName[12], pathToContent[1000], ch, nextBlock[10];
    char allContents[1000][12];
    int i;
    fstream F;
    
    strcpy(pathToContentBlock, pathToFS);
    strcat(pathToContentBlock, contentBlock);
    F.open(pathToContentBlock);
    F >> nextBlock;
    ch = F.get();
    ch = F.peek();
    while (ch == '/') {
        F >> contentName;
        
        strcpy(pathToContent, initialPathInFS);
        strcat(pathToContent, contentName);
        if (remove(pathToFS, pathToContent) != 0) {
            cout << "Mistake\n";
            return 1;
        }
        F.close();
        F.open(pathToContentBlock);
        F >> nextBlock;
        
        ch = F.get();
        ch = F.peek();
        // skips eol and '/' to next content
    }
    F.close();
    if (removeFile(pathToFS, initialPathInFS) != 0) {
        cout << "Mistake\n";
        return 1;
    }
    
    return 0;
}

int remove(char *pathToFS, char *pathInFS)
{
    bool isFolder, isFile;
    
    if (pathInFS[strlen(pathInFS)-1] == '/') {
        isFolder = true;
        isFile = false;
    } else {
        isFolder = false;
        isFile = true;
    }
    if (isFile == true) {
        if (removeFile(pathToFS, pathInFS) != 0) {
            cout << "Mistake\n";
            return 1;
        }
    } else {
        if (removeFolder(pathToFS, pathInFS) != 0) {
            cout << "Mistake\n";
            return 1;
        }
    }
    return 0;
}
